﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

using ProjectBase.Core;

namespace $safeprojectname$
{
    /// <summary>
    /// Provides serialization for Components which are mapped as IList of its holder (no ID in fact)
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TID"></typeparam>
    public class EntityResolver<TEntity, TID> : DefaultContractResolver, IRequestResolver
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        #region members
        IList<string> _ignoredProperties;
        IList<string> _allowedProperties;
        MetaDataMessage _metaData;
        IList<MDAttribute> _attributes;
        #endregion members

        #region constructor
        public EntityResolver()
        {
            IgnoreSerializableAttribute = true;
        }
        #endregion constructor

        #region override
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var propertyInfo = member as PropertyInfo;
            if (propertyInfo.IsNull())
            {
                return null;
            }

            // any other serialization 
            // BUT Persistent objects 
            // goes standard way (e.g. ServiceMessage...)
            if (!member.DeclaringType.HasType<IPersistentObject>())
            {
                return base.CreateProperty(member, memberSerialization);
            }

            // Ignored properties (managed in a business layer) won't be processed
            if (IgnoredProperties.Contains(member.Name))
            {
                return null;
            }

            // Business rules can set some properties as Allowed (only)
            var isAllowed = AllowedProperties.IsEmpty()
                || AllowedProperties.Contains(member.Name)
                || AllowedProperties.Any(p => p.StartsWith(member.Name + '.'));

            if (!isAllowed)
            {
                return null;
            }

            var jProperty = base.CreateProperty(member, memberSerialization);

            jProperty = DecorateProperty(jProperty, propertyInfo);

            ApplyMetaData(jProperty);

            return jProperty;
        }
        protected override JsonConverter ResolveContractConverter(Type objectType)
        {
            if (objectType.HasType<TEntity>())
            {
                EntityConverter.Attributes = Attributes;
                return EntityConverter;
            }
            return base.ResolveContractConverter(objectType);
        }
        #endregion override

        #region Decorate
        protected virtual JsonProperty DecorateProperty(JsonProperty jProperty, PropertyInfo propertyInfo)
        {
            var wasDecorated = DecorateValueTypes(jProperty, propertyInfo)
                               || DecorateChildren(jProperty, propertyInfo)
                               || DecoratePersistentProperty(jProperty, propertyInfo);

            if (wasDecorated)
            {
                return jProperty;
            }

            return null;
        }

        protected virtual bool DecorateValueTypes(JsonProperty jProperty, PropertyInfo propertyInfo)
        {
            // string, structs are OK
            var isBasicType = propertyInfo.PropertyType.IsEquivalentTo(typeof(string))
                              || propertyInfo.PropertyType.IsValueType;
            if (isBasicType)
            {
                return true;
            }
            return false;
        }

        protected virtual bool DecorateChildren(JsonProperty jProperty, PropertyInfo propertyInfo)
        {
            // Hierarchy as an array of ID
            var isChildCollection = propertyInfo.PropertyType.IsGenericType
                                    && propertyInfo.PropertyType.HasType(typeof (IEnumerable))
                                    && propertyInfo.PropertyType.GetGenericArguments().First().HasType(propertyInfo.ReflectedType);

            if (!isChildCollection)
            {
                return false;
            }

            // Do not render Array unless Allowed or for Detail
            var skipArrayRendering = DoNotRenderArray
                                     || (AllowedProperties.IsNotEmpty()
                                         && !AllowedProperties.Any(p => p.StartsWith(propertyInfo.Name)));

            if (skipArrayRendering)
            {
                jProperty.Ignored = true;
                return true;
            }

            jProperty.Writable = false;
            jProperty.ValueProvider = new ChildrenValueProvider(propertyInfo);
            jProperty.PropertyType = typeof (int[]);
            return true;
        }

        protected virtual bool DecoratePersistentProperty(JsonProperty jProperty, PropertyInfo propertyInfo)
        {
            var isReferencedPersistent = propertyInfo.PropertyType.IsClass
                                         && propertyInfo.PropertyType.HasType<IPersistentObject>();

            if (!isReferencedPersistent)
            {
                return false;
            }

            var innerProperties = AllowedProperties
                .Where(p => p.StartsWith(propertyInfo.Name + '.'))
                .OrderBy(p => p)
                .ToList();

            // skip it unless explitly in the $select
            jProperty.NullValueHandling = innerProperties.IsEmpty() ? NullValueHandling.Ignore : NullValueHandling.Include;

            jProperty.Writable = false;
            jProperty.Readable = true;
            jProperty.ValueProvider = new PersistentValueProvider(propertyInfo, innerProperties);
            jProperty.PropertyType = typeof(object);

            return true;

        }
        #endregion Decorate

        #region Apply MetaData
        protected virtual void ApplyMetaData(JsonProperty jProperty)
        {
            if (jProperty.IsNull())
            {
                return;
            }

            var attribute = GetAttribute(jProperty);
            if (attribute.IsNull())
            {
                jProperty.Writable = false;
                jProperty.Readable = false;
                return;
            }

            jProperty.Writable &= attribute.CanEdit;
            jProperty.Readable &= attribute.CanRead;
        }
        protected virtual MDAttribute GetAttribute(JsonProperty jProperty)
        {
            var attribute = Attributes
                    .FirstOrDefault(a => a.Name.IsEqual(jProperty.PropertyName))
                    ;

            return attribute;
        }
        #endregion Apply MetaData

        #region Properties

        public virtual IList<string> IgnoredProperties
        {
            get
            {
                if (_ignoredProperties.IsNull())
                {
                    _ignoredProperties = MetaData.IgnoredProperties;
                }
                return _ignoredProperties;

            }
            set { _ignoredProperties = value; }
        }
        public virtual IList<string> AllowedProperties
        {
            get { return _allowedProperties ?? (_allowedProperties = new List<string>()); }
            set 
            { 
                _allowedProperties = value; 
                if (_allowedProperties.Is())
                {
                    DoNotRenderArray = true;
                }
            }
        }
        public virtual bool DoNotRenderArray { get; set; }

        protected virtual MetaDataMessage MetaData
        {
            get
            {
                var metaData = _metaData;
                if (metaData.IsNull())
                {
                    TID id;
                    if (EntityConverter.TryGetCurrentActionID(out id))
                    {
                        metaData = MetaDataProvider.GetMetaData(id);
                    }
                    else
                    {
                        metaData = MetaDataProvider.GetMetaData();
                    }
                    _metaData = metaData;
                }
                return metaData;
            }
            set { _metaData = value; }
        }
        public virtual IList<MDAttribute> Attributes
        {
            get { return _attributes ?? MetaData.Attributes; }
            set { _attributes = value; }
        }

        public virtual IMetaDataProvider<TEntity, TID> MetaDataProvider { get; set; }

        public virtual EntityConverter<TEntity, TID> EntityConverter { get; set; }
        #endregion Properties
    }

}